import { performance } from 'node:perf_hooks'

import type { BaseWindow, WebContentsView } from 'electron'

/**
 * Easing 函数类型
 */
export type EasingFn = (t: number) => number

/**
 * 过渡选项
 */
export interface AnimateBoundsOptions {
  /** 动画时长（毫秒）默认 220ms */
  duration?: number
  /** 缓动函数，默认 easeInOutCubic */
  easing?: EasingFn
  /** 需要参与动画的属性，默认 ['x','y','width','height'] */
  properties?: Array<'x' | 'y' | 'width' | 'height'>
  /** 每帧回调（可选） */
  onUpdate?: (bounds: RectLike) => void
  /** 完成回调（可选） */
  onComplete?: () => void
  /** 若已有动画是否打断并重启（默认 true） */
  interrupt?: boolean
  /** 与目标差值在该阈值内则直接跳到终点（像素），默认 0 */
  snapThreshold?: number
  /** 是否启用动画；为 false 或 duration<=0 时直接 setBounds，默认 true */
  enabled?: boolean
}

/**
 * 与 Electron Rectangle 兼容的简化类型
 */
export interface RectLike {
  x?: number
  y?: number
  width: number
  height: number
}

/**
 * 常用 easing
 */
export const Easings = {
  linear: (t: number) => t,
  easeInOutCubic: (t: number) => (t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2),
  easeOutCubic: (t: number) => 1 - Math.pow(1 - t, 3)
} as const

/**
 * 内部：判断目标是否已销毁
 */
function isTargetDestroyed(target: BaseWindow | WebContentsView) {
  // BaseWindow (BrowserWindow) 具有 isDestroyed
  const anyTarget: any = target as any
  if (typeof anyTarget.isDestroyed === 'function') {
    return !!anyTarget.isDestroyed()
  }
  // WebContentsView 通过 webContents.isDestroyed()
  if (anyTarget.webContents && typeof anyTarget.webContents.isDestroyed === 'function') {
    return !!anyTarget.webContents.isDestroyed()
  }
  return false
}

/**
 * 获取当前 bounds（两种目标都支持 getBounds）
 */
function getCurrentBounds(target: BaseWindow | WebContentsView): RectLike {
  // Electron 的 BaseWindow 与 WebContentsView 都有 getBounds()
  const b = (target as any).getBounds() as RectLike
  // 保证字段存在
  return {
    x: typeof b.x === 'number' ? b.x : 0,
    y: typeof b.y === 'number' ? b.y : 0,
    width: b.width,
    height: b.height
  }
}

/**
 * 设置 bounds（两种目标都支持 setBounds）
 */
function setTargetBounds(target: BaseWindow | WebContentsView, rect: RectLike) {
  ;(target as any).setBounds(rect)
}

/**
 * 每个目标只保留一个运行中的动画，后来的动画按 options.interrupt 处理
 */
const running = new WeakMap<BaseWindow | WebContentsView, { cancel: () => void }>()

/**
 * 取消某目标当前动画
 */
export function cancelBoundsAnimation(target: BaseWindow | WebContentsView) {
  const r = running.get(target)
  if (r) {
    r.cancel()
    running.delete(target)
  }
}

/**
 * 为 setBounds 增加缓动过渡
 * - 默认 220ms easeInOutCubic
 * - 支持中断与阈值直达
 * - 目标既可为 BaseWindow 也可为 WebContentsView
 */
export async function animateBounds(
  target: BaseWindow | WebContentsView,
  to: RectLike,
  options: AnimateBoundsOptions = {}
): Promise<void> {
  const {
    duration = 220,
    easing = Easings.easeInOutCubic,
    properties = ['x', 'y', 'width', 'height'],
    onUpdate,
    onComplete,
    interrupt = true,
    snapThreshold = 0,
    enabled = true
  } = options

  if (isTargetDestroyed(target)) return

  const from = getCurrentBounds(target)
  const toFixed: RectLike = { ...from, ...to }

  const existing = running.get(target)

  // 若禁用动画或 duration<=0，直接设置终点，并取消现有动画
  if (!enabled || duration <= 0) {
    if (existing) {
      existing.cancel()
      running.delete(target)
    }
    if (!isTargetDestroyed(target)) setTargetBounds(target, toFixed)
    onUpdate?.(toFixed)
    onComplete?.()
    return
  }

  if (existing) {
    if (interrupt) {
      existing.cancel()
      running.delete(target)
    } else {
      // 不打断则直接返回，让现有动画继续
      return
    }
  }

  // 若差值很小，直接设置终点
  const deltaMax = Math.max(
    ...(['x', 'y', 'width', 'height'] as const).map(key =>
      Math.abs(((toFixed as any)[key] ?? 0) - ((from as any)[key] ?? 0))
    )
  )
  if (deltaMax <= snapThreshold) {
    if (!isTargetDestroyed(target)) setTargetBounds(target, toFixed)
    onUpdate?.(toFixed)
    onComplete?.()
    return
  }

  let frameTimer: NodeJS.Timeout | null = null
  let cancelled = false
  const start = performance.now()

  const cancel = () => {
    cancelled = true
    if (frameTimer) clearTimeout(frameTimer)
  }

  running.set(target, { cancel })

  return await new Promise<void>(resolve => {
    const tick = () => {
      if (cancelled) return resolve()
      if (isTargetDestroyed(target)) return resolve()

      const now = performance.now()
      const t = Math.min(1, (now - start) / duration)
      const k = easing(t)

      const next: RectLike = { ...from }
      for (const p of properties) {
        const a = (from as any)[p] ?? 0
        const b = (toFixed as any)[p] ?? 0
        ;(next as any)[p] = Math.round(a + (b - a) * k)
      }

      setTargetBounds(target, next)
      onUpdate?.(next)

      if (t >= 1) {
        running.delete(target)
        onComplete?.()
        return resolve()
      }

      // 约 60fps
      frameTimer = setTimeout(tick, 1000 / 60)
    }

    tick()
  })
}
